-------------------------------------------------------------------------------
-- cache_controller_rtl.vhd
-------------------------------------------------------------------------------
--
-- This file is part of SKUMLI.
-- Copyright (C) 2011 Davide Giuseppe Monaco (black.ralkass@gmail.com)
--
-- SKUMLI is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- SKUMLI is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with SKUMLI.  If not, see <http://www.gnu.org/licenses/>.
--
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;
-------------------------------------------------------------------------------
architecture rtl of cache_controller is

  signal state     : std_logic_vector(2 downto 0);

  signal serve     : std_logic; -- simulates TO CPU -> IDLE transition

  signal lwrite    : std_logic; -- use to flag last write (of four)

  signal wc_addr   : std_logic_vector(31 downto 0); -- write cache addr
  signal rmam_addr : std_logic_vector(31 downto 0); -- read mem after miss addr
  signal wv_addr   : std_logic_vector(31 downto 0); -- write victim cache addr
  signal wm_addr   : std_logic_vector(31 downto 0); -- write mem address

  signal ctr       : integer; -- counter (write cache)
  signal vctr      : integer; -- counter (write victim cache)
  signal dctr      : integer; -- counter (write victim cache)
  signal mctr      : integer; -- counter (write back)

  signal c_din_sel : std_logic_vector(1 downto 0); -- cache data in selector (mem or victim)
  signal c_addr_sel : std_logic_vector(1 downto 0);

begin -- rtl
-------------------------------------------------------------------------------

  wc_addr(31 downto 4)   <= addr(31 downto 4);
  rmam_addr(31 downto 4) <= addr(31 downto 4);

  -- wv_addr is the address which represents the OLD block, so I have to 
  -- retrieve the OLD TAG. INDEX is the same of current address.
  -- OFFSET is calculated in wv_addr_gen.
  wv_addr(31 downto 9)   <= c_tab(24 downto 2);
  wv_addr( 8 downto 4)   <= addr(8 downto 4);

  --
  wm_addr(31 downto 9)   <= v_tab(29 downto 7);
  wm_addr( 8 downto 4)   <= v_tab( 6 downto 2);

--  dout <= c_dout;
  
  mux_ch_dout_sel <= addr(3 downto 2); -- XXX verify

-------------------------------------------------------------------------------
  m_addr_gen: process (state, mctr)

    variable a : std_logic_vector(31 downto 0);

  begin -- m_addr_gen

    if state = "110" then
      a := rmam_addr;
    elsif state = "101" then
      a := wm_addr;
    else
      a := (others => '0');
    end if;

    m_addr <= a;

  end process m_addr_gen;
-------------------------------------------------------------------------------
  wm_addr_gen: process (state, mctr)

    variable offset : std_logic_vector(3 downto 0) := (others => '0');

  begin -- wm_addr_gen

    if state = "001" then
      offset := "0000";
    
    elsif state = "101" then

      if mctr = 0 then
        offset := "0100";

      elsif mctr = 1 then
        offset := "1000";

      elsif mctr = 2 then
        offset := "1100"; 

      else
        offset := (others => '0');
      end if;

    end if;

    wm_addr(3 downto 0) <= offset;
    
  end process wm_addr_gen;

-------------------------------------------------------------------------------
  -- v_set_d is HIGH if dirty bit in cache table is HIGH.
  v_set_d_gen: process (state, c_tab)
    variable d : std_logic;
  begin -- v_set_d_gen

    if state = "001" and c_tab(0) = '1' then
      d := '1';
    else
      d := '0';
    end if;

    v_set_d <= d;

  end process v_set_d_gen;

-------------------------------------------------------------------------------
  -- c_din_src is HIGH if write comes from CPU. This is the case in which
  -- dirty bit must be set HIGH. (ie: store instruction write back)
  -- There's another case: restore a block from victim cache.
  c_din_src_gen: process (state, rw)

    variable d : std_logic := '0';
    variable wb : std_logic := '0';

  begin -- c_din_src_gen

    if state = "111" and wb = '0' and
       (rw = '1' or v_tab_buf(0) = '1') then -- d-bit on d-victim cache restore
      d := '1';

    elsif state = "101" then -- set wb state if 'WRITE BACK'
      d := '0';
      wb := '1';

    elsif state = "100" then -- reset wb state after 'TO CPU'
      wb := '0';

    else
      d := '0';

    end if;

    c_din_src <= d;

  end process c_din_src_gen;

-------------------------------------------------------------------------------
  c_addr_gen: process (state, c_rdy)
    variable a : std_logic_vector(31 downto 0);
  begin -- c_addr_gen
    if state = "111" then
      a := wc_addr;
    else
      a := addr;
    end if;

    c_addr <= a;
  end process c_addr_gen;

-------------------------------------------------------------------------------
  -- v_rdy is in sensitivity list because the process must trigger when there
  -- are events on the signal.
  v_addr_gen: process (state, v_rdy)

    variable a : std_logic_vector(31 downto 0);

  begin -- v_addr_gen

    if state = "001" then
      a := wv_addr;
    else
      a := addr;
    end if;

    v_addr <= a;

  end process v_addr_gen;

-------------------------------------------------------------------------------
  v_set_v_gen: process (state, vctr)

    variable v : std_logic := '0';

  begin -- v_set_v_gen

    if state = "001" and vctr = 3 then 
      v := '1';
    else
      v := '0';
    end if;

    v_set_v <= v;

  end process v_set_v_gen;

-------------------------------------------------------------------------------
  in_buf_sel_gen: process (state, vctr)

    variable sel : std_logic_vector(1 downto 0) := "00";

  begin -- in_buf_sel_gen

    if state = "001" then
      
      if vctr = 0 then
        sel := "00";

      elsif vctr = 1 then
        sel := "01";

      elsif vctr = 2 then
        sel := "10"; 

      elsif vctr = 3 then
        sel := "11";

      else
        sel := "00";
      end if;

    end if;

    mux_in_buf_sel <= sel;

  end process in_buf_sel_gen;

-------------------------------------------------------------------------------
  out_buf_sel_gen: process (state, dctr, mctr)

    variable sel : std_logic_vector(1 downto 0) := "00";

  begin -- out_buf_sel_gen

    if state = "111" then
      
      if dctr = 0 then
        sel := "00";

      elsif dctr = 1 then
        sel := "01";

      elsif dctr = 2 then
        sel := "10"; 

      elsif dctr = 3 then
        sel := "11";

      else
        sel := "00";
      end if;

    elsif state = "101" then

      if mctr = 0 then
        sel := "00";

      elsif mctr = 1 then
        sel := "01";

      elsif mctr = 2 then
        sel := "10"; 

      elsif mctr = 3 then
        sel := "11";

      else
        sel := "00";
      end if;

    end if;

    mux_out_buf_sel <= sel;

  end process out_buf_sel_gen;

-------------------------------------------------------------------------------
  wv_addr_gen: process (state, vctr)

    variable offset : std_logic_vector(3 downto 0) := (others => '0');

  begin -- wv_addr_gen

    if state = "001" then

      if vctr = 0 then
        offset := "0100";

      elsif vctr = 1 then
        offset := "1000";

      elsif vctr = 2 then
        offset := "1100";

      else
        offset := "0000";

      end if;

    end if;

    wv_addr(3 downto 0) <= offset;

  end process wv_addr_gen;

-------------------------------------------------------------------------------
  mux_ch_din_sel_gen: process (state, c_din_sel)

    variable sel : std_logic_vector(1 downto 0);

  begin -- mux_ch_din_sel_gen

    if state = "111" and c_din_sel = "01" then
      sel := "01";

    elsif state = "111" and c_din_sel = "10" then
      sel := "10";

    else
      sel := "00";

    end if;

    mux_ch_din_sel <= sel;

  end process mux_ch_din_sel_gen;

-------------------------------------------------------------------------------
  -- read mem after miss address selector
  rmam_addr_gen: process (state, ctr)

    variable offset : std_logic_vector(3 downto 0) := (others => '0');

  begin -- rmam 

    if state = "111" then

      if ctr = 0 then
        offset := "0000";

      elsif ctr = 1 then
        offset := "0100";

      elsif ctr = 2 then
        offset := "1000";

      elsif ctr = 3 then
        offset := "1100";

      else
        offset := "0000";

      end if;

    end if;

    rmam_addr(3 downto 0) <= offset;

  end process rmam_addr_gen;

-------------------------------------------------------------------------------
  -- write cache address selector
  wc_addr_gen: process (state, ctr, dctr, c_rdy, rw, addr)

    variable offset : std_logic_vector(3 downto 0) := (others => '0');

  begin -- wc_addr_gen

    if state = "001" then

      offset := "0000";

    elsif state = "010" then       -- during READ CACHE maintain original address

      offset := addr(3 downto 0);

    elsif state = "111" then

      if rw = '1' then
        offset := addr(3 downto 0);

      elsif dctr = 0 then
        offset := "0100";

      elsif dctr = 1 then
        offset := "1000";

      elsif dctr = 2 then
        offset := "1100";

      else 
        offset := "0000";

      end if;

    elsif state = "110" then

      if ctr = 0 then
        offset := "0000";

      elsif ctr = 1 then
        offset := "0100";

      elsif ctr = 2 then
        offset := "1000";

      elsif ctr = 3 then
        offset := "1100";

      else
        offset := "0000";

      end if;

    end if;

    wc_addr(3 downto 0) <= offset;

  end process wc_addr_gen;

-------------------------------------------------------------------------------
  -- set valid bit HIGH if state is write cache and last write flag is HIGH.
  -- lwrite is HIGH if current block to write is the 4th.
  set_v_gen: process (state, lwrite)

    variable v : std_logic;

  begin -- set_v_gen

    if lwrite = '1' and state = "111" then
      v := '1';
    else
      v := '0';
    end if;

    c_set_v <= v;

  end process set_v_gen;

-------------------------------------------------------------------------------
  -- this process is used for simulation purpose 
  serve_gen: process (state)
  begin -- serve_gen 

    if state = "100" then
      serve <= '1', '0' after 10 ns; -- simulate component delay

    else
      serve <= '0';

    end if;

  end process serve_gen;

-------------------------------------------------------------------------------
  -- This process handles cache controller FSM.
  --
  main: process (state, cs, rw, serve,
                 c_rdy, c_hit, c_tab,
                 v_rdy, v_hit, v_tab,
                 m_rdy)

    variable init    : boolean := false;
    variable s       : std_logic_vector(2 downto 0); -- next state
    variable c       : integer := 0; -- write cache counter
    variable d       : integer := 0; -- write cache (from victim) counter
    variable v       : integer := 0; -- write victim cache counter
    variable m       : integer := 0; -- write mem counter
    variable last    : std_logic; -- last write (for mem block write)
    variable hit     : std_logic; -- cache hit
    variable cmiss   : std_logic; -- compulsory miss
    variable miss    : std_logic; -- cache miss
    variable vmiss   : std_logic; -- victim cache miss
    variable vhit    : std_logic; -- victim cache miss
    variable wb      : std_logic; -- victim cache miss
    variable din_sel : std_logic_vector(1 downto 0); -- cache data in selector

  begin -- main

    -- initialization process. 
    if init = false then
      s       := "000";
      last    := '0';
      hit     := '0';
      cmiss   := '0';
      miss    := '0';
      vmiss   := '0';
      vhit    := '0';
      wb      := '0';
      din_sel := "00";
      init    := true;
    end if;

    -- FSM
    case state is

        -- IDLE
      when "000" =>
        hit := '0'; -- reset hit flag if write from CPU is performed

        -- set next state
        if cs'event and cs = '1' then -- READ CACHE
          s := "010";
        else  
          s := "000";
        end if;
        --

        -- READ CACHE
      when "010" =>

        -- reset counters and flags
        last    := '0';
        vhit    := '0'; -- used to switch MUX_CH_DIN
        vmiss   := '0';
        wb      := '0';
        d       := 0;
        --

        -- set hit, miss or compulsory miss flag
        if c_hit = '1' and c_tab(1) = '1' then     -- cache hit
          hit := '1';

        elsif c_hit = '0' and c_tab(1) = '1' then  -- cache miss
          miss := '1';

        elsif c_hit = '0' and c_tab(1) = '0' then  -- compulsory miss
          cmiss := '1';
          miss  := '0'; -- XXX: cache works 'after 1 ns'

        end if;
        --

        -- set next state
        -- 
        -- XXX: in the last two `elsif`, 'rw' is not checked
        --      to allow FSM proceed in the right way when WRITE
        --      is requested.
        if cs = '1' and rw = '0' and
           c_rdy'event and c_rdy = '1' and hit = '1' then -- TO CPU

          s := "100";

        elsif cs = '1' and rw = '1' and
              c_rdy'event and c_rdy = '1' and hit = '1' then -- WRITE CACHE

          last := '1';
          s := "111";

        elsif cs = '1' and
              c_rdy'event and c_rdy = '1' and miss = '1' then -- READ VICTIM CACHE

          s := "011";

        elsif cs = '1' and
              c_rdy'event and c_rdy = '1' and cmiss = '1' then -- READ MEM AFTER MISS

          s := "110";

        else
          s := "010";

        end if;
        --

        -- READ MEM AFTER MISS
      when "110" =>

        -- set 'cache data in selector` properly
        din_sel := "01";
        --

        -- set next state
        if m_rdy'event and m_rdy = '1' and
           cs = '1' then -- WRITE CACHE

          c := c + 1;
          s := "111";

          -- set flag to specify last write
          if c = 4 then
            last := '1';
          end if;
          --

        else
          s := "110";
        end if;
        --

        -- WRITE CACHE
      when "111" =>

        -- set 'cache data in selector` properly
        if vhit = '1' then
          din_sel := "10";

        elsif hit = '1' and rw = '1' then
          din_sel := "00";

        else
          din_sel := "01";

        end if;
        --
        
        -- set next state
        -- 
        -- TODO: fix double read cache down here
        if c_rdy'event and c_rdy = '1' and 
           rw = '1' and hit = '1' then -- TO CPU

          s := "100";

        elsif c_rdy'event and c_rdy = '1' and 
              vhit = '1' and d < 3 then -- WRITE CACHE
          d := d + 1;
          s := "111";

          if d = 2 then
            last := '1';
          end if;

        elsif c_rdy'event and c_rdy = '1' and 
              vhit = '1' and d = 3 then -- READ CACHE

          s := "010";

        elsif c_rdy'event and c_rdy = '1' and
              cs = '1' and c < 4 then -- READ MEM AFTER MISS
          s := "110";

        elsif c_rdy'event and c_rdy = '1' and
              cs = '1' and c = 4 then -- READ CACHE
          c := 0;
          s := "010";

        else
          s := "111";

        end if;
        --

        -- TO CPU
      when "100" =>
        hit   := '0';
        miss  := '0';
        cmiss := '0';

        if serve'event and serve = '0' then -- IDLE
          s := "000";
        else
          s := "100";
        end if;

        -- READ VICTIM CACHE
      when "011" =>

        -- victim flag
        if v_hit = '1' then
          vhit := '1';
        elsif v_hit = '0' and v_tab(0) = '1' then
          wb := '1';
        elsif v_hit = '0' and v_tab(0) = '0' then
          vmiss := '1';
          wb    := '0'; -- victim cache works 'after 1 ns', so be sure to have right vmiss/wb
        end if;
        --

        if v_rdy'event and v_rdy = '1' then
          s := "001";
        else
          s := "011";
        end if;

        -- WRITE VICTIM CACHE
      when "001" =>
        if v_rdy'event and v_rdy = '1' and v < 3 then -- continue writing
          v := v + 1;
          s := "001";

        elsif v_rdy'event and v_rdy = '1' and v = 3 then -- last write
          v := 0;

          if vhit = '1' then -- WRITE CACHE
            s := "111";
          elsif wb = '1' then -- WRITE BACK MEM
            wb := '0';
            s  := "101";
          elsif vmiss = '1' then -- READ MEM AFTER MISS
            vmiss := '0'; -- reset victim cache miss flag
            s     := "110";
          end if;

        else
          s := "001";
        end if;

        -- WRITE MEM (WRITE BACK)
      when "101" =>
        if m_rdy'event and m_rdy = '1' and m < 3 then -- continue writing
          m := m + 1;
          s := "101";

        elsif m_rdy'event and m_rdy = '1' and m = 3 then -- last write
          m := 0;
          s := "110";

        else
          s := "101";
        end if;

      when others =>
        s := "000";

    end case;

    state     <= s;
    lwrite    <= last;
    c_din_sel <= din_sel;
    ctr       <= c;
    vctr      <= v;
    dctr      <= d;
    mctr      <= m;

  end process main;

-------------------------------------------------------------------------------
  -- This process handles signal-per-state behaviour for those signals 
  -- which do not need an internal bus.
  --
  cmb: process (state)
  begin -- cmb

    case state is

      -- IDLE
      when "000" =>
        dout            <= (others => '0');
        rdy             <= '1';
        m_rw            <= '0';
        m_cs            <= '0';
        v_rw            <= '0';
        v_cs            <= '0';
        c_rw            <= '0';
        c_cs            <= '0';
        in_buf0_we      <= '0';
        in_buf1_we      <= '0';
        in_buf2_we      <= '0';
        in_buf3_we      <= '0';
        out_buf0_we     <= '0';
        out_buf1_we     <= '0';
        out_buf2_we     <= '0';
        out_buf3_we     <= '0';

      -- READ CACHE
      when "010" =>
        dout            <= (others => '0');
        rdy             <= '0';
        m_rw            <= '0';
        m_cs            <= '0';
        v_rw            <= '0';
        v_cs            <= '0';
        c_rw            <= '0';
        c_cs            <= '1';
        in_buf0_we      <= '1';
        in_buf1_we      <= '1';
        in_buf2_we      <= '1';
        in_buf3_we      <= '1';
        v_tab_buf_we    <= '0';
        out_buf0_we     <= '0';
        out_buf1_we     <= '0';
        out_buf2_we     <= '0';
        out_buf3_we     <= '0';

      -- READ MEM AFTER MISS
      when "110" =>
        dout            <= (others => '0');
        rdy             <= '0';
        m_rw            <= '0';
        m_cs            <= '1';
        v_rw            <= '0';
        v_cs            <= '0';
        c_rw            <= '0';
        c_cs            <= '0';
        in_buf0_we      <= '0';
        in_buf1_we      <= '0';
        in_buf2_we      <= '0';
        in_buf3_we      <= '0';
        v_tab_buf_we    <= '0';
        out_buf0_we     <= '0';
        out_buf1_we     <= '0';
        out_buf2_we     <= '0';
        out_buf3_we     <= '0';

      -- WRITE CACHE
      when "111" =>
        dout            <= (others => '0');
        rdy             <= '0';
        m_rw            <= '0';
        m_cs            <= '0';
        v_rw            <= '0';
        v_cs            <= '0';
        c_rw            <= '1';
        c_cs            <= '1';
        in_buf0_we      <= '0';
        in_buf1_we      <= '0';
        in_buf2_we      <= '0';
        in_buf3_we      <= '0';
        v_tab_buf_we    <= '0';
        out_buf0_we     <= '0';
        out_buf1_we     <= '0';
        out_buf2_we     <= '0';
        out_buf3_we     <= '0';

      -- TO CPU
      when "100" =>
        dout            <= c_dout;
        rdy             <= '1';
        m_rw            <= '0';
        m_cs            <= '0';
        v_rw            <= '0';
        v_cs            <= '0';
        c_rw            <= '0';
        c_cs            <= '0';
        in_buf0_we      <= '0';
        in_buf1_we      <= '0';
        in_buf2_we      <= '0';
        in_buf3_we      <= '0';
        v_tab_buf_we    <= '0';
        out_buf0_we     <= '0';
        out_buf1_we     <= '0';
        out_buf2_we     <= '0';
        out_buf3_we     <= '0';

      -- READ VICTIM CACHE
      when "011" =>
        dout            <= (others => '0');
        rdy             <= '0';
        m_rw            <= '0';
        m_cs            <= '0';
        v_rw            <= '0';
        v_cs            <= '1';
        c_rw            <= '0';
        c_cs            <= '0';
        in_buf0_we      <= '0';
        in_buf1_we      <= '0';
        in_buf2_we      <= '0';
        in_buf3_we      <= '0';
        v_tab_buf_we    <= '1';
        out_buf0_we     <= '1';
        out_buf1_we     <= '1';
        out_buf2_we     <= '1';
        out_buf3_we     <= '1';

      -- WRITE VICTIM CACHE
      when "001" =>
        dout            <= (others => '0');
        rdy             <= '0';
        m_rw            <= '0';
        m_cs            <= '0';
        v_rw            <= '1';
        v_cs            <= '1';
        c_rw            <= '0';
        c_cs            <= '0';
        in_buf0_we      <= '0';
        in_buf1_we      <= '0';
        in_buf2_we      <= '0';
        in_buf3_we      <= '0';
        v_tab_buf_we    <= '0';
        out_buf0_we     <= '0';
        out_buf1_we     <= '0';
        out_buf2_we     <= '0';
        out_buf3_we     <= '0';

      -- WRITE MEM (WRITE BACK)
      when "101" =>
        dout            <= (others => '0');
        rdy             <= '0';
        m_rw            <= '1';
        m_cs            <= '1';
        v_rw            <= '0';
        v_cs            <= '0';
        c_rw            <= '0';
        c_cs            <= '0';
        in_buf0_we      <= '0';
        in_buf1_we      <= '0';
        in_buf2_we      <= '0';
        in_buf3_we      <= '0';
        v_tab_buf_we    <= '0';
        out_buf0_we     <= '0';
        out_buf1_we     <= '0';
        out_buf2_we     <= '0';
        out_buf3_we     <= '0';

      when others =>
        dout            <= (others => '0');
        rdy             <= '0';
        m_rw            <= '0';
        m_cs            <= '0';
        v_rw            <= '0';
        v_cs            <= '0';
        c_rw            <= '0';
        c_cs            <= '0';
        in_buf0_we      <= '0';
        in_buf1_we      <= '0';
        in_buf2_we      <= '0';
        in_buf3_we      <= '0';
        v_tab_buf_we    <= '0';
        out_buf0_we     <= '0';
        out_buf1_we     <= '0';
        out_buf2_we     <= '0';
        out_buf3_we     <= '0';

    end case;

  end process cmb;
-------------------------------------------------------------------------------

end rtl;
